package ntu.metaweb;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashSet;

import ntu.metaweb.http.MetawebHTTPHandler;
import ntu.metaweb.http.Server;
import ntu.metaweb.storage.MetawebObjectDeserializer;
import ntu.metaweb.storage.User;
import ntu.metaweb.update.MetawebDifferenceDeserializer;
import ntu.metaweb.update.MetawebUpdateApplicator;
import ntu.p2p.dht.DHT;
import ntu.p2p.dht.DHTImpl;
import ntu.p2p.past.messaging.UpdateObjectMessage;
import rice.environment.Environment;
import rice.p2p.past.Past;
import rice.pastry.NodeIdFactory;
import rice.pastry.PastryNode;
import rice.pastry.PastryNodeFactory;
import rice.pastry.commonapi.PastryIdFactory;
import rice.pastry.socket.SocketPastryNodeFactory;
import rice.pastry.standard.RandomNodeIdFactory;
import rice.persistence.LRUCache;
import rice.persistence.MemoryStorage;
import rice.persistence.Storage;
import rice.persistence.StorageManagerImpl;

public class Metaweb {
	private static Metaweb instance;
	
	private Server httpServer;
	private DHT dhtApplication;
	
	private HashSet<User> loggedinUser;
	
  private Metaweb(int httpport, int bindport, InetSocketAddress bootaddress, 
  		final Environment env) throws Exception {
  	this.loggedinUser = new HashSet<User>();
  	
  	// Create http server
  	this.httpServer = new Server(new MetawebHTTPHandler(), httpport);
  	
    // Generate the NodeIds Randomly
    NodeIdFactory nidFactory = new RandomNodeIdFactory(env);

    // construct the PastryNodeFactory, this is how we use rice.pastry.socket
    PastryNodeFactory factory = new SocketPastryNodeFactory(nidFactory,
        bindport, env);

    // cause the node to start its own ring
    PastryNode node = factory.newNode();

      // used for generating PastContent object Ids.
      // this implements the "hash function" for our DHT
      PastryIdFactory idf = new rice.pastry.commonapi.PastryIdFactory(env);
      
      // create a different storage root for each node
      String storageDirectory = "./storage"+node.getId().hashCode();

      // create the persistent part
      Storage stor = new MemoryStorage(idf);
      this.dhtApplication = new DHTImpl(node, 
      		new StorageManagerImpl(idf, stor, new LRUCache(
          new MemoryStorage(idf), 512 * 1024, node.getEnvironment())), 0, "",
          new MetawebUpdateApplicator());
      
      this.dhtApplication.setContentDeserializer(new MetawebObjectDeserializer(),
      		new MetawebDifferenceDeserializer());
         

      node.boot(bootaddress);
  }
  
  public static void createInstance(int httpport, int bindport, InetSocketAddress bootaddress,
  		final Environment env) throws Exception {
  	if (Metaweb.instance == null) {
  		Metaweb.instance = new Metaweb(httpport, bindport, bootaddress, env);
  	}
  }
  
  public static Metaweb getInstance() {
  	return Metaweb.instance;
  }
  
  public Server getHttpServer() {
  	return this.httpServer;
  }
  
  public DHT getDhtApplication() {
  	return this.dhtApplication;
  }
  
  public static void main(String[] args) throws Exception {
    // Loads pastry configurations
    Environment env = new Environment();

    // disable the UPnP setting (in case you are testing this on a NATted LAN)
    env.getParameters().setString("nat_search_policy","never");
    
    try {
    	// the port for http server
    	int httpport = Integer.parseInt(args[0]);
    	
      // the port to use locally
      int bindport = Integer.parseInt(args[1]);

      // build the bootaddress from the command line args
      InetAddress bootaddr = InetAddress.getByName(args[2]);
      int bootport = Integer.parseInt(args[3]);
      InetSocketAddress bootaddress = new InetSocketAddress(bootaddr, bootport);

      // launch our node!
      Metaweb.createInstance(httpport, bindport, bootaddress, env);
      
    } catch (Exception e) {
      // remind user how to use
      System.out.println("Usage:");
      System.out
          .println("java [-cp FreePastry-<version>.jar] rice.tutorial.past.PastTutorial localbindport bootIP bootPort numNodes");
      System.out
          .println("example java rice.tutorial.past.PastTutorial 9001 pokey.cs.almamater.edu 9001 10");
      throw e;
    }
  }
  
  public HashSet<User> getLoggedinUser() {
  	return this.loggedinUser;
  }
}
